﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ShredTheLog.Api;
using ShredTheLog.Api.DataContracts;
using System.ComponentModel.Composition;
using System.Windows.Threading;
using System.Windows;
using System.IO;
using System.Runtime.Serialization.Formatters.Soap;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using ShredTheLog.Common.MVVM;
using ShredTheLog.Views;
using ShredTheLog.ViewModels;
using System.Collections.ObjectModel;
using ShredTheLog.Controls;
using System.Media;
using Hardcodet.Wpf.TaskbarNotification;
using System.Windows.Media;

namespace ShredTheLog.Plugins
{
    [Export(typeof(IShredServices))]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public sealed class ShredServices : IShredServices
    {
        [Import]
        private IShredSettings ShredSettings { get; set; }

        [Import]
        private IUiSettingsSection UiSettings { get; set; }

        [ImportMany]
        private IUiLanguageStrings[] UiLanguageStrings { get; set; }

        [ImportMany]
        private ISkinPart[] SkinParts { get; set; }

        [ImportMany]
        private INotifier[] Notifiers { get; set; }

        public ShredServices(ResourceDictionary resources, Func<string> getSettingsString, Action<string> saveSettingsString)
        {
            Contract.Requires(resources != null);
            Contract.Requires(getSettingsString != null);
            Contract.Requires(saveSettingsString != null);

            _resources = resources;
            _getSettingsString = getSettingsString;
            _saveSettingsString = saveSettingsString;
            resources.MergedDictionaries.Add(_skinDictionary);
        }

        public void Run()
        {
            Trace.WriteLineIf(TraceFlags.Initialization, "Starting...");
            LoadSettings();

            var appIcon = (ImageSource)new ImageSourceConverter().ConvertFromInvariantString(@"pack://application:,,,/ShredTheLog;component/ShredTheLog.ico");
            _notifyIcon = new TaskbarIcon() { IconSource = appIcon };

            Notifiers.ForEach(i => i.Start()); 
            MainViewModel = new MainViewModel();
            new MainView().Show(MainViewModel);
        }

        private ResourceDictionary _resources;
        private Func<string> _getSettingsString;
        private Action<string> _saveSettingsString;

        private ResourceDictionary _skinDictionary = new ResourceDictionary();

        private TaskbarIcon _notifyIcon;

        public MainViewModel MainViewModel { get; private set; }

        public ITextChat TextChat
        {
            get 
            {
                var textChatViewModel = MainViewModel.TextChat;
                var textChat = textChatViewModel != null ? textChatViewModel.Model : null;
                return (ITextChat)textChat;
            }
        }

        public void OpenTextChatRoom(string roomName)
        {
            var window = Application.Current.Windows.OfType<TextChatRoomView>().Where(i => ((TextChatRoomViewModel)i.DataContext).RoomName == roomName).SingleOrDefault();
            if (window == null)
                new TextChatRoomView().Show(new TextChatRoomViewModel(roomName));
            else
                window.Activate();
        }

        public void ShowError(Exception ex)
        {
            ShowError(() => GetLocalizedString("s_UnknownError"), ex);
        }

        public void ShowError(Func<string> message, Exception ex)
        {
            ShowError(message, () => ex.ToString());
        }

        public void ShowError(Func<string> message, Func<string> details)
        {
            new ErrorView().Show(new ErrorViewModel(message, details));
        }

        public void ShowMessage(Func<string> message)
        {
            new MessageDialogView().Show(new MessageDialogViewModel(message));
        }

        public void ShowInfoBalloonTip(string title, string message)
        {
            _notifyIcon.ShowBalloonTip(title, message, BalloonIcon.Info);
        }

        public string GetLocalizedString(string key)
        {
            try
            {
                string result = _localizedStrings[key];
                Contract.Assume(result != null);
                return result;
            }
            catch (KeyNotFoundException ex)
            {
                throw new KeyNotFoundException(String.Format("{0} (key: {1})", ex.Message, key ?? String.Empty), ex);
            }
        }

        public void LoadSettings()
        {
            Trace.WriteLineIf(TraceFlags.Initialization, "Loading settings...");
            var serializedSettings = _getSettingsString();
            if (!String.IsNullOrEmpty(serializedSettings))
            {
                try
                {
                    using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(serializedSettings)))
                    {
                        SoapFormatter formatter = new SoapFormatter();
                        var shredSettings = (ShredSettings)formatter.Deserialize(stream);
                        if (shredSettings != null)
                            ShredSettings.Assign(shredSettings);
                    }
                }
                catch (Exception ex)
                {
                    Trace.WriteLine("Failed to restore application settings.");
                    Trace.WriteLine(ex.ToString());
                }
            }
            OnSettingsUpdated();
        }

        public void SaveSettings(bool raiseOnUpdated = true)
        {
            using (var stream = new MemoryStream())
            {
                SoapFormatter formatter = new SoapFormatter();
                formatter.Serialize(stream, ShredSettings);
                stream.Seek(0, SeekOrigin.Begin);
                using (var reader = new StreamReader(stream))
                {
                    var serializedSettings = reader.ReadToEnd();
                    _saveSettingsString(serializedSettings);
                }
            }

            if (raiseOnUpdated)
                OnSettingsUpdated();
        }

        private void OnSettingsUpdated()
        {
            Trace.WriteLineIf(TraceFlags.Settings, "Applying settings...");
            
            UpdateTheme();
            UpdateUiLanguage();

            if (SettingsUpdated != null)
                SettingsUpdated(this, EventArgs.Empty);
        }

        private void UpdateTheme()
        {
            var theme = UiSettings.GetTheme();
            Func<IEnumerable<ISkinPart>, Uri> getValue =
                skinParts =>
                {
                    Contract.Requires(skinParts != null);
                    Contract.Requires(Contract.ForAll(skinParts, i => i != null));
                    Contract.Ensures(Contract.Result<Uri>() != null);

                    var result = skinParts.SingleOrDefault(i => i.ThemeID == theme.ID);
                    if (result == null)
                        result = skinParts.SingleOrDefault(i => i.ThemeID == null);
                    return result.PartUri;
                };

            Contract.Assume(SkinParts != null);
            var skinUris =
                from i in SkinParts
                where i.ThemeID == theme.ID || i.ThemeID == null
                group i by i.PartID into partGroup
                select getValue(partGroup);

            _skinDictionary.MergedDictionaries.Clear();
            skinUris.ForEach(i => _skinDictionary.MergedDictionaries.Add(new ResourceDictionary() { Source = i }));
        }

        private void UpdateUiLanguage()
        {
            var uiLanguage = UiSettings.GetUiLanguage();
            Trace.WriteLineIf(TraceFlags.Settings, String.Format("UiLanguage.Language: {0}", uiLanguage.Language.ToString()));
            Func<IEnumerable<Tuple<Language, string>>, string> getValue = 
                s =>
                {
                    // Сперва ищем данную строку на языке выбранном в настройках...
                    var result = s.SingleOrDefault(i => i.Item1 == uiLanguage.Language);
                    if (result != null)
                        return result.Item2;
                    // Если не нашли, то ищем строку на языках подстановки...
                    foreach (var substituteLanguage in uiLanguage.SubstituteLanguages)
                    {
                        result = s.SingleOrDefault(i => i.Item1 == substituteLanguage);
                        if (result != null)
                            return result.Item2;
                    }
                    // Хотя бы в английском переводе пытаемся найти...
                    result = s.SingleOrDefault(i => i.Item1 == Language.English);
                    if (result != null)
                        return result.Item2;
                    // Или в русском...
                    result = s.SingleOrDefault(i => i.Item1 == Language.Russian);
                    if (result != null)
                        return result.Item2;
                    // Ничего не нашли, вернем первую попавшуюся...
                    return s.First().Item2;
                };

            Contract.Assume(UiLanguageStrings != null);
            _localizedStrings =
                (from ls in UiLanguageStrings
                 from cs in
                     (from s in ls.GetStrings() select new { Language = ls.Language, Key = s.Key, Value = s.Value })
                 group new Tuple<Language, string>(cs.Language, cs.Value) by cs.Key into g
                 select new { Key = g.Key, Value = getValue(g) })
                 .ToDictionary(i => i.Key, i => i.Value);
        }

        public event EventHandler SettingsUpdated;

        Dictionary<string, string> _localizedStrings;

        public Dispatcher Dispatcher 
        { 
            get 
            {
                var result = Application.Current.Dispatcher;
                Contract.Assume(result != null);
                return result;
            } 
        }

        public void Shutdown()
        {
            Application.Current.Shutdown();
        }

        public ReadOnlyObservableCollection<IContact> Contacts { get { return MainViewModel.Contacts; } }

        public ReadOnlyObservableCollection<IShortUserInfo> TextChatUsers 
        { 
            get { return MainViewModel.TextChat != null ? MainViewModel.TextChat.Users : null; } 
        }

        public void Dispose()
        {
            Notifiers.ForEach(i => i.Dispose());
        }
    }
}
